/**
 * Copyright (c) 2005-2012 springside.org.cn
 */
package com.kingbook.common.security;

import com.kingbook.common.exception.Exceptions;

import javax.crypto.KeyGenerator;
import javax.crypto.Mac;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import java.io.IOException;
import java.io.InputStream;
import java.security.*;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;

/**
 * 消息摘要的工具类.（将任意长输入通过算法变为固定长输出，且保证输入任何变化，输出都会不同，且不能反向解密）
 * 支持SHA-1，MD5，HmacSHA1, SHA1withRSA
 * SHA-1基于MD5,是一种应用最为广泛的Hash函数算法，也是目前最先进的加密技术.它对长度小于264的输入，产生长度为160bit的散列值，因此抗穷举(brute-force)性更好。
 * MD5(RFC1321)是对MD4的改进版本。它对输入仍以512位分组，其输出是4个32位字的级联,是一种不可逆的加密算法,一般用于密码的加密。
 * HmacSHA1: HMAC是密钥相关的哈希运算消息认证码（Hash-based Message Authentication Code）,HMAC运算利用哈希算法，以一个密钥和一个消息为输入，生成一个消息摘要作为输出。
 *
 */
public class DigestsManager {

    private static final String SHA1 = "SHA-1";
    private static final String MD5 = "MD5";
    private static final String HMACSHA1 = "HmacSHA1";
    private static final String RSA = "RSA";
    private static final String SHA1WITHRSA = "SHA1WithRSA";
    private static final int DEFAULT_HMACSHA1_KEYSIZE = 160; //RFC2401
    private static final byte[] SALT = new byte[]{22, -45, 78, 29, -10, 76, -24, 13, -76, 126, 25, 0, 3, -20, 113, 108};
    private static final int ITERATIONS = 16;

    private static SecureRandom random = new SecureRandom();

    /**
     * 对输入字符串进行md5散列.
     *
     * @param input 需要签名的源数据
     * @return
     */
    public static String md5(byte[] input) {
        return EncoderManager.encodeHex(digest(input, MD5, null, 0));
    }

    /**
     * 对输入字符串进行md5散列.
     *
     * @param input      需要签名的源数据
     * @param salt       初始向量
     * @param iterations 加密次数
     */
    public static String md5(byte[] input, byte[] salt, int iterations) {
        return EncoderManager.encodeHex(digest(input, MD5, salt, iterations));
    }

    /**
     * 对文件进行md5散列.
     *
     * @param input
     * @return md5签名
     */
    public static String md5(InputStream input) throws IOException {
        return EncoderManager.encodeHex(digest(input, MD5));
    }

    /**
     * 对输入字符串进行sha1散列.
     *
     * @param input 需要签名的源数据
     * @return
     */
    public static String sha1(byte[] input) {
        return EncoderManager.encodeHex(digest(input, SHA1, null, 0));
    }

    /**
     * 对输入字符串进行sha1散列.
     *
     * @param input      需要签名的源数据
     * @param salt       初始向量
     * @param iterations 加密次数
     * @return
     */
    public static String sha1(byte[] input, byte[] salt, int iterations) {
        return EncoderManager.encodeHex(digest(input, SHA1, salt, iterations));
    }

    /**
     * 对文件进行sha1散列.
     *
     * @param input
     * @return sha1签名
     */
    public static String sha1(InputStream input) throws IOException {
        return EncoderManager.encodeHex(digest(input, SHA1));
    }

    /**
     * 生成随机salt,最小16字节.
     *
     * @param saltSize 需要生成初始化向量的位数
     * @return 初始向量
     */
    public static byte[] genSalt(int saltSize) {
        int size = saltSize < 16 ? 16 : saltSize;
        byte[] bytes = new byte[size];
        new SecureRandom().nextBytes(bytes);
        return bytes;
    }

    /**
     * 使用HMAC-SHA1进行消息签名, 返回字节数组,长度为20字节.
     * HMAC-SHA1是一种安全的基于加密 hash函数和共享密钥的消息认证协议，它可以有效地防止数据在传输的过程中被截取和篡改，维护了数据的完整性、可靠性和安全性。
     * HMAC-SHA1消息认证机制的成功在于一个加密的 hash函数、一个加密的随机密钥和一个安全的密钥交换机制。
     * HMAC-SHA1计算字符串的签名，需要一个密钥，一般用来作服务器端的签名验证
     *
     * @param input 原始输入字符数组
     * @param key   HMAC-SHA1密钥
     * @return HmacSha1签名
     */
    public static String hmacSha1(byte[] input, String key) {
        try {
            SecretKey secretKey = new SecretKeySpec(EncoderManager.decodeHex(key), HMACSHA1);
            Mac mac = Mac.getInstance(HMACSHA1);
            mac.init(secretKey);
            return EncoderManager.encodeHex(mac.doFinal(input));
        } catch (GeneralSecurityException e) {
            throw Exceptions.unchecked(e);
        }
    }

    /**
     * 校验HMAC-SHA1签名是否正确.
     *
     * @param input     原始输入字符串
     * @param signature HmacSha1后的签名
     * @param key       HmacSha1Key时的秘钥
     * @return
     */
    public static boolean checkHmacSha1(byte[] input, String signature, String key) {
        String actual = hmacSha1(input, key);
        System.out.println("actual:" + actual);
        return actual.equals(signature);
    }

    /**
     * 生成HMAC-SHA1密钥,返回字节数组,长度为160位(20字节).
     * HMAC-SHA1算法对密钥无特殊要求, RFC2401建议最少长度为160位(20字节).
     *
     * @return 返回一个随机的HmacSha1Key
     */
    public static String genHmacSha1Key() {
        try {
            KeyGenerator keyGenerator = KeyGenerator.getInstance(HMACSHA1);
            keyGenerator.init(DEFAULT_HMACSHA1_KEYSIZE);
            SecretKey secretKey = keyGenerator.generateKey();
            return EncoderManager.encodeHex(secretKey.getEncoded());
        } catch (GeneralSecurityException e) {
            throw Exceptions.unchecked(e);
        }
    }

    public static String genSha1withRsaSign(String data, String privateKey) {
        try {
//            KeyPairGenerator gen = KeyPairGenerator.getInstance("RSA");
//            gen.initialize(2048);
//            KeyPair pair = gen.generateKeyPair();
//            //rsa生成一对公私钥
//            PublicKey publicKey  = pair.getPublic();
//            PrivateKey privateKey  = pair.getPrivate();
            KeyFactory keyf = KeyFactory.getInstance(RSA);
            PrivateKey priKey = keyf.generatePrivate(new PKCS8EncodedKeySpec(EncoderManager.decodeBase64(privateKey.getBytes())));
            //SHA1withRSA算法进行签名
            Signature sign = Signature.getInstance(SHA1WITHRSA);
            sign.initSign(priKey);
            //更新用于签名的数据
            sign.update(data.getBytes());
            return EncoderManager.encodeBase64String(sign.sign());
        } catch (Exception e) {
            throw Exceptions.unchecked(e);
        }
    }

    public static boolean checkSha1withRsaSign(String data, String sign, String publicKey) {
        try {
            KeyFactory keyf = KeyFactory.getInstance(RSA);
            PublicKey pubKey = keyf.generatePublic(new X509EncodedKeySpec(EncoderManager.decodeBase64(publicKey.getBytes())));
            Signature signature = Signature.getInstance(SHA1WITHRSA);
            signature.initVerify(pubKey);
            signature.update(data.getBytes());
            return signature.verify(EncoderManager.decodeBase64(sign.getBytes()));
        } catch (Exception e) {
            throw Exceptions.unchecked(e);
        }
    }

    /**
     * 对字符数组进行散列, 支持md5与sha1算法.
     */
    private static byte[] digest(byte[] input, String algorithm, byte[] salt, int iterations) {
//        salt = (salt == null || salt.length < SALT.length) ? SALT : salt;
//        iterations = iterations < ITERATIONS ? ITERATIONS : iterations;
        try {
            MessageDigest digest = MessageDigest.getInstance(algorithm);
            if (salt != null)
                digest.update(salt);
            byte[] result = digest.digest(input);
            for (int i = 0; i < iterations; i++) {
                digest.reset();
                result = digest.digest(result);
            }
            return result;
        } catch (GeneralSecurityException e) {
            throw Exceptions.unchecked(e);
        }
    }


    /**
     * 对流进行散列
     *
     * @param input
     * @param algorithm
     * @return
     * @throws IOException
     */
    private static byte[] digest(InputStream input, String algorithm) {
        try {
            MessageDigest messageDigest = MessageDigest.getInstance(algorithm);
            byte[] buffer = new byte[8192];   //8k
            for (int read = -1; (read = input.read(buffer, 0, 8192)) > -1; ) {
                messageDigest.update(buffer, 0, read);
            }
            return messageDigest.digest();
        } catch (IOException | GeneralSecurityException e) {
            throw Exceptions.unchecked(e);
        }
    }


}
